#include "rpc_statistics_impl.hh"
#include <limits>
#include <list>
#include <mutex>
#include <thread>

namespace rpc {

class MethodCallStatisticsImpl : public rpc::MethodCallStatistics {
  std::uint32_t methodID_{0};      ///< Method ID
  std::uint32_t qps_{0};           ///< Request count/Sec.
  std::uint32_t peak_qps_{0};      ///< Maximum request count/Sec.
  std::time_t min_cost_{0};        ///< Miniimum method cost(Millionseconds)
  std::time_t max_cost_{0};        ///< Maximum method cost(Millionseconds)
  std::time_t avgCost_{0};         ///< Avg. method cost(Millionseconds)
  std::time_t last_tick_{0};       ///< Last tick when invoking
  std::time_t acum_tick_{0};       ///< Accumulated tick period
  std::uint32_t call_count_{0};    ///< Total call count
  std::uint32_t success_count_{0}; ///< Successful call count
  std::string method_full_name_;   ///< Method full name

public:
  MethodCallStatisticsImpl(std::uint32_t methodID, const std::string& method_full_name);
  virtual ~MethodCallStatisticsImpl();
  virtual std::uint32_t getMethodID() const noexcept override;
  virtual std::uint32_t getQPS() const noexcept override;
  virtual std::uint32_t getPeakQPS() const noexcept override;
  virtual std::time_t getMinCost() const noexcept override;
  virtual std::time_t getMaxCost() const noexcept override;
  virtual std::time_t getAvgCost() const noexcept override;
  virtual std::uint32_t getCallCount() const noexcept override;
  virtual std::uint32_t getCallSuccessCount() const noexcept override;
  virtual const std::string& getMethodFullName() const noexcept override;
  void doStat(std::time_t begin_tick, std::time_t end_tick, bool success);
};

class ServiceCallStatisticsImpl : public rpc::ServiceCallStatistics {
  std::uint64_t uuid_{0};                  ///< Service UUID
  rpc::MethodStatistics methodStatistics_; ///< Method statistics
  std::uint32_t qps_{0};                   ///< Request count/Sec.
  std::uint32_t peak_qps_{0};              ///< Maximum request count/Sec.
  std::time_t min_cost_{0};        ///< Miniimum method cost(Millionseconds)
  std::time_t max_cost_{0};        ///< Maximum method cost(Millionseconds)
  std::time_t avgCost_{0};         ///< Avg. method cost(Millionseconds)
  std::time_t last_tick_{0};       ///< Last tick when invoking
  std::time_t acum_tick_{0};       ///< Accumulated tick period
  std::uint32_t call_count_{0};    ///< Total call count
  std::uint32_t success_count_{0}; ///< Successful call count

public:
  ServiceCallStatisticsImpl(std::uint64_t uuid);
  virtual ~ServiceCallStatisticsImpl();
  virtual std::uint64_t getUUID() const noexcept override;
  virtual const rpc::MethodStatistics &
  getMethodStatistics() const noexcept override;
  virtual const rpc::MethodCallStatistics *
  getMethodCallStatistics(std::uint32_t methodID) const noexcept override;
  virtual std::uint32_t getQPS() const noexcept override;
  virtual std::uint32_t getPeakQPS() const noexcept override;
  virtual std::uint32_t getCallCount() const noexcept override;
  virtual std::uint32_t getCallSuccessCount() const noexcept override;
  virtual std::time_t getMinCost() const noexcept override;
  virtual std::time_t getMaxCost() const noexcept override;
  virtual std::time_t getAvgCost() const noexcept override;
  void doStat(std::uint32_t methodID, std::time_t begin_tick,
              std::time_t end_tick, const std::string& method_full_name, bool success);
};

} // namespace rpc

rpc::MethodCallStatisticsImpl::MethodCallStatisticsImpl(
    std::uint32_t methodID, const std::string& method_full_name) {
  methodID_ = methodID;
  method_full_name_ = method_full_name;
}

rpc::MethodCallStatisticsImpl::~MethodCallStatisticsImpl() {}

std::uint32_t rpc::MethodCallStatisticsImpl::getMethodID() const noexcept {
  return methodID_;
}

std::uint32_t rpc::MethodCallStatisticsImpl::getQPS() const noexcept {
  return qps_;
}

std::uint32_t rpc::MethodCallStatisticsImpl::getPeakQPS() const noexcept {
  return peak_qps_;
}

std::time_t rpc::MethodCallStatisticsImpl::getMinCost() const noexcept {
  return min_cost_;
}

std::time_t rpc::MethodCallStatisticsImpl::getMaxCost() const noexcept {
  return max_cost_;
}

std::time_t rpc::MethodCallStatisticsImpl::getAvgCost() const noexcept {
  return avgCost_;
}

std::uint32_t rpc::MethodCallStatisticsImpl::getCallCount() const noexcept {
  return call_count_;
}

std::uint32_t
rpc::MethodCallStatisticsImpl::getCallSuccessCount() const noexcept {
  return success_count_;
}

const std::string& rpc::MethodCallStatisticsImpl::getMethodFullName() const noexcept {
    return method_full_name_;
}

void rpc::MethodCallStatisticsImpl::doStat(std::time_t begin_tick,
                                           std::time_t end_tick, bool success) {
  if (success) {
    success_count_ += 1;
  }
  call_count_ += 1;
  auto gap = end_tick - begin_tick;
  last_tick_ = end_tick;
  if (0 == min_cost_) {
    min_cost_ = gap;
  } else if (gap < min_cost_) {
    min_cost_ = gap;
  }
  if (0 == max_cost_) {
    max_cost_ = gap;
  } else if (max_cost_ < gap) {
    max_cost_ = gap;
  }
  acum_tick_ += gap;
  avgCost_ = (std::time_t)((double)acum_tick_ / (double)success_count_);
  // Microseconds to seconds
  auto seconds = (double)acum_tick_ / 1000000.0f;
  qps_ = (std::uint32_t)((double)success_count_ / seconds);
  if (0 == peak_qps_) {
    peak_qps_ = qps_;
  } else if (qps_ > peak_qps_) {
    peak_qps_ = qps_;
  }
}

rpc::ServiceCallStatisticsImpl::ServiceCallStatisticsImpl(std::uint64_t uuid) {
  uuid_ = uuid;
}

rpc::ServiceCallStatisticsImpl::~ServiceCallStatisticsImpl() {
  for (auto it : methodStatistics_) {
    delete it.second;
  }
  methodStatistics_.clear();
}

std::uint64_t rpc::ServiceCallStatisticsImpl::getUUID() const noexcept {
  return uuid_;
}

const rpc::MethodStatistics &
rpc::ServiceCallStatisticsImpl::getMethodStatistics() const noexcept {
  return methodStatistics_;
}

const rpc::MethodCallStatistics *
rpc::ServiceCallStatisticsImpl::getMethodCallStatistics(
    std::uint32_t methodID) const noexcept {
  auto it = methodStatistics_.find(methodID);
  if (it == methodStatistics_.end()) {
    return nullptr;
  }
  return it->second;
}

std::uint32_t rpc::ServiceCallStatisticsImpl::getQPS() const noexcept {
  return qps_;
}

std::uint32_t rpc::ServiceCallStatisticsImpl::getPeakQPS() const noexcept {
  return peak_qps_;
}

std::uint32_t rpc::ServiceCallStatisticsImpl::getCallCount() const noexcept {
  return call_count_;
}

std::uint32_t
rpc::ServiceCallStatisticsImpl::getCallSuccessCount() const noexcept {
  return success_count_;
}

std::time_t rpc::ServiceCallStatisticsImpl::getMinCost() const noexcept {
  return min_cost_;
}

std::time_t rpc::ServiceCallStatisticsImpl::getMaxCost() const noexcept {
  return max_cost_;
}

std::time_t rpc::ServiceCallStatisticsImpl::getAvgCost() const noexcept {
  return avgCost_;
}

void rpc::ServiceCallStatisticsImpl::doStat(std::uint32_t methodID,
                                            std::time_t begin_tick,
                                            std::time_t end_tick,
                                            const std::string& method_full_name,
                                            bool success) {
  auto it = methodStatistics_.find(methodID);
  if (it == methodStatistics_.end()) {
    auto result = methodStatistics_.emplace(
        methodID, new MethodCallStatisticsImpl(methodID, method_full_name));
    it = result.first;
    return;
  }
  auto &method_stat = dynamic_cast<MethodCallStatisticsImpl &>(*it->second);
  method_stat.doStat(begin_tick, end_tick, success);
  if (success) {
    success_count_ += 1;
  }
  call_count_ += 1;
  auto gap = end_tick - begin_tick;
  last_tick_ = end_tick;
  if (0 == min_cost_) {
    min_cost_ = gap;
  } else if (gap < min_cost_) {
    min_cost_ = gap;
  }
  if (0 == max_cost_) {
    max_cost_ = gap;
  } else if (max_cost_ < gap) {
    max_cost_ = gap;
  }
  acum_tick_ += gap;
  avgCost_ = (std::time_t)((double)acum_tick_ / (double)success_count_);
  // Microseconds to seconds
  auto seconds = (double)acum_tick_ / 1000000.0f;
  qps_ = (std::uint32_t)((double)success_count_ / seconds);
  if (0 == peak_qps_) {
    peak_qps_ = qps_;
  } else if (qps_ > peak_qps_) {
    peak_qps_ = qps_;
  }
}

rpc::StubCallStatisticsImpl::StubCallStatisticsImpl() {}

rpc::StubCallStatisticsImpl::~StubCallStatisticsImpl() {
  for (auto it : serviceStatistics_) {
    delete it.second;
  }
  serviceStatistics_.clear();
}

bool rpc::StubCallStatisticsImpl::start(std::uint32_t maxQpsLimit,
                                        std::uint32_t maxQueueSizeLimit) {
  peak_qps_limit_ = maxQpsLimit;
  max_queue_size_limit_ = maxQueueSizeLimit;
  return true;
}

bool rpc::StubCallStatisticsImpl::stop() { return true; }

bool rpc::StubCallStatisticsImpl::checkHealthy(std::uint64_t uuid,
                                               std::uint32_t queueSize,
                                               std::time_t tick) {
  auto it = serviceStatistics_.find(uuid);
  if (it == serviceStatistics_.end()) {
    return false;
  }
  if (0 == last_tick_) {
    return true;
  }
  if (queueSize >= max_queue_size_limit_) {
    return false;
  }
  if (qps_ >= peak_qps_limit_) {
    return false;
  }
  return true;
}

void rpc::StubCallStatisticsImpl::addStatistics(std::uint64_t uuid,
                                                std::uint32_t methodID,
                                                std::time_t begin_tick,
                                                std::time_t end_tick,
                                                const std::string& method_full_name,
                                                bool success) {
  auto it = serviceStatistics_.find(uuid);
  if (it == serviceStatistics_.end()) {
    auto result =
        serviceStatistics_.emplace(uuid, new ServiceCallStatisticsImpl(uuid));
    it = result.first;
    return;
  }
  auto &service_stat = dynamic_cast<ServiceCallStatisticsImpl &>(*it->second);
  service_stat.doStat(methodID, begin_tick, end_tick, method_full_name, success);
  if (success) {
    success_count_ += 1;
  }
  call_count_ += 1;
  auto gap = end_tick - begin_tick;
  last_tick_ = end_tick;
  if (0 == min_cost_) {
    min_cost_ = gap;
  } else if (gap < min_cost_) {
    min_cost_ = gap;
  }
  if (0 == max_cost_) {
    max_cost_ = gap;
  } else if (max_cost_ < gap) {
    max_cost_ = gap;
  }
  acum_tick_ += gap;
  avgCost_ = (std::time_t)((double)acum_tick_ / (double)success_count_);
  // Microseconds to seconds
  auto seconds = (double)acum_tick_ / 1000000.0f;
  qps_ = (std::uint32_t)((double)success_count_ / seconds);
  if (0 == peak_qps_) {
    peak_qps_ = qps_;
  } else if (qps_ > peak_qps_) {
    peak_qps_ = qps_;
  }
}

const rpc::ServiceCallStatistics *
rpc::StubCallStatisticsImpl::getServiceCallStatistics(
    std::uint64_t uuid) const noexcept {
  auto it = serviceStatistics_.find(uuid);
  if (it == serviceStatistics_.end()) {
    return nullptr;
  }
  return it->second;
}

const rpc::ServiceStatistics *
rpc::StubCallStatisticsImpl::getServiceStatistics() const noexcept {
  return &serviceStatistics_;
}

const rpc::MethodCallStatistics *
rpc::StubCallStatisticsImpl::getMethodCallStatistics(
    std::uint64_t uuid, std::uint32_t methodID) const noexcept {
  auto it = serviceStatistics_.find(uuid);
  if (it == serviceStatistics_.end()) {
    return nullptr;
  }
  return it->second->getMethodCallStatistics(methodID);
}

const rpc::MethodStatistics *rpc::StubCallStatisticsImpl::getMethodStatistics(
    std::uint64_t uuid) const noexcept {
  auto it = serviceStatistics_.find(uuid);
  if (it == serviceStatistics_.end()) {
    return nullptr;
  }
  return &it->second->getMethodStatistics();
}

rpc::StubCallStatisticsImpl *global_stat_impl = nullptr;

rpc::StubCallStatistics *rpc::getStatistic() {
  if (!global_stat_impl) {
    if (!initializeStatistics(std::numeric_limits<std::uint32_t>::max(),
                              std::numeric_limits<std::uint32_t>::max())) {
      return nullptr;
    }
  }
  return global_stat_impl;
}

bool rpc::initializeStatistics(std::uint32_t maxQps,
                               std::uint32_t maxQueueSizeLimit) {
  global_stat_impl = new rpc::StubCallStatisticsImpl();
  return global_stat_impl->start(maxQps, maxQueueSizeLimit);
}

void rpc::uninitializeStatistics() {
  if (global_stat_impl) {
    delete global_stat_impl;
  }
  global_stat_impl = nullptr;
}

rpc::StubCallStatisticsImpl *rpc::getStatisticImpl() {
  return dynamic_cast<rpc::StubCallStatisticsImpl *>(getStatistic());
}
